 1.类与对象之类和无参构造器
   
   在Scala中，类并不用声明为public；
   Scala源文件中可以包含多个类，所有这些类都具有公有可见性；
   val修饰的变量（常量），值不能改变，只提供getter方法，没有setter方法；
   var修饰的变量，值可以改变，对外提供getter、setter方法；
   如果没有定义构造器，类会有一个默认的无参构造器；

package lagou.cn.part04

import java.awt.TrayIcon.MessageType

//在Scala中，类都有一个无参构造器
class Person{
  //声明字段必须进行初始化，Scala编译器会根据初始化值的数据类型
  //自动推断字段的类型，字段类型可以省略
  var name="lagou"

  //_表示一个 占位符，编译器会根据变量的数据类型赋予相应的初始值
  //注意：使用占位符_进行赋初始值时，数据类型必须指定
  var nickName: String = _
  var numInt: Int = _
  var numDouble: Double = _
  var boolean: Boolean = _

  //val修饰的变量不能使用占位符
//  val test:Int=_
  var num=30

  var age=20
  //如果赋值为null，就需要添加数据类型。
  //如果不添加数据类型，那么就会认为是Null类型的
  var address: String =null
  //类中的私有字段，有私有的getter和setter方法
  //可以在类的内部访问，也可以被器伴生对象访问
  private var hobby="旅游"
  //对象私有字段，访问权限更小，只能在当前类中访问
  private [this] val cardInfo="10010"

  //自定义方法
  def hello(message: String): Unit ={
    //只能在当前类中访问cardInfo
    println(s"$message, $cardInfo")
  }
  //自定义方法实现两数求和
  def addNum(num1: Int, num2: Int): Int = {
    num1 + num2
  }
}
object ClassDemo {
  def main(args: Array[String]): Unit = {
    //使用类的无参构造器来创建对象
    val person = new Person()
    //小括号也是可以省略的
    val person2=new Person
    println(s"${person.nickName}  ${person.numInt}  ${person.numDouble}  ${person.boolean}")

    //给类的属性赋值
    person.age=50
    //注意：如果使用对象的属性加上 _= 给var修饰的属性进行赋值
    //其实就是调用age_=这个setter方法
    person.age_=(20)
    //调用类的属性，其实就是调用它的getter方法
    println(person.age)

    //调用类中的方法
    person.hello("hello")
    println(person.addNum(10, 20))
  }
}


 2.自定义getter和setter方法
   
   对于 Scala 类中的每一个属性，编译后会有一个私有的字段和相应的getter、setter方法生成。
   //getter方法
println(person age)
   
   //setter方法
person age_= (18)
   
   //getter方法
println(person.age)
   
   可以不使用自动生成的方式，自己定义getter和setter方法

package lagou.cn.part04

class Dog {
  private var _leg = 0
  //自定义getter方法
  def leg:Int=_leg

  //自定义的setter方法
  def leg_=(newLeg: Int): Unit = {
    _leg = newLeg
  }
}

object GetterAndSetterDemo {
  def main(args: Array[String]): Unit = {
    val dog=new Dog
    //调用自定义的setter方法
    dog.leg_=(4)
    //调用自定义的getter方法
    println(dog.leg)

  }

}

  
  自定义变量的getter和setter方法需要遵循以下原则：
      字段属性名以“_”作为前缀，如： _leg
      getter方法定义为：def leg = _leg
      setter方法定义为：def leg_=(newLeg: Int)

 3.Bean属性
   
   JavaBean规范把Java属性定义为一堆getter和setter方法。
   类似于Java，当将Scala字段标注为 @BeanProperty时，getFoo和setFoo方法会自动生成。
   使用@BeanProperty并不会影响Scala自己自动生成的getter和setter方法。
   在使用时需要导入包scala.beans.BeanProperty

package lagou.cn.part04
//需要导入下面的包
import scala.beans.BeanProperty
class Teacher{
  @BeanProperty var name: String = _

}

object BeanDemo {
  def main(args: Array[String]): Unit = {
    val teacher=new Teacher
    teacher.name="jacky"
    teacher.name_=("tom")
    println(teacher.name)
    //BeanProperty生成的setName方法
    teacher.setName("lisi")
    //BeanProperty生成的getName
    println(teacher.getName)

  }

}


    上述Teacher类中共生成了四个方法：
	1. name: String
    2. name_= (newValue: String): Unit
    3. getName(): String
    4. setName (newValue: String): Unit
	
 4.构造器
   
   如果没有定义构造器，Scala类中会有一个默认的无参构造器；
   Scala当中类的构造器分为两种：主构造器和辅助构造器；
   主构造器的定义与类的定义交织在一起，将主构造器的参数直接放在类名之后。
   当主构造器的参数不用var或val修饰时，参数会生成类的私有val成员。
   Scala中，所有的辅助构造器都必须调用另外一个构造器，另外一个构造器可以是辅助构造器，也可以
是主构造器。
   
package lagou.cn.part04

//主构造器与类名交织在一起，类名后面的参数即为主构造器的参数
//主构造器直接在类中，其代码不包含在任何方法中
class Animal(name: String, age: Int) {
  //下面三行println代码都属于主构造器的代码
  println(name)
  println(age)
  println("============================")

  var gender: String = ""

  def this(name: String, age: Int, gender: String) {
    //每个辅助构造器必须以主构造器或其他辅助构造器的调用作为第一句代码
    this(name, age)
    this.gender = gender
  }

  var color: String = ""

  def this(name: String, age: Int, gender: String, color: String) {
    //此处调用上面的辅助构造器
    this(name, age, gender)
    this.color = color
  }
}

object ConstructorDemo {
  def main(args: Array[String]): Unit = {
    val animal = new Animal("狗蛋", 4)
    val animal2 = new Animal("旺财", 3, "雄性")
    val animal3 = new Animal("小六", 5, "雄性", "黑色")

  }

}


5.对象
  
  1).单例对象
  Scala并没有提供Java那样的静态方法或静态字段；
  可以采用object关键字实现单例对象，具备和Java静态方法同样的功能；
  使用object语法结构【object是Scala中的一个关键字】达到静态方法和静态字段的目的；对象本质上可
以拥有类的所有特性，除了不能提供构造器参数；
  对于任何在Java中用单例对象的地方，在Scala中都可以用object实现：
      作为存放工具函数或常量的地方
      高效地共享单个不可变实例

class Session {
  def hello(first: Int): Int = {
    println(first)
    first
  }
}

object SessionFactory {
  val session = new Session

  def getSession(): Session = {
    session
  }

  def main(args: Array[String]): Unit = {
    for (x <- 1 to 10) {
      //通过直接调用，产生的对象都是单例的
	  val session = SessionFactory.getSession()
      println(session)
    }
  }
} 
  Scala中的单例对象具有如下特点：
    (1)、创建单例对象不需要使用new关键字
    (2)、object中只有无参构造器
    (3)、主构造代码块只能执行一次，因为它是单例的

object ObjectDemo {
   println("这是单例对象的代码！")
   
   def printerInfo: Unit = {
    println("Hello Scala Object!")
  }
}
   def main(args: Array[String]): Unit = {
     val object1=ObjectDemo
     val object2=ObjectDemo
	 
	 Object.printerInfo
     Object.printerInfo
   }
}
  2).伴生类与伴生对象
  当单例对象与某个类具有相同的名称时，它被称为这个类的“伴生对象”；
  类和它的伴生对象必须存在于同一个文件中，而且可以相互访问私有成员（字段和方法）；

class ClassObject {
  val id = 1
  private var name = "lagou"
  def printName(): Unit ={
  //在ClassObject类中可以访问伴生对象ClassObject的私有字段
  println(ClassObject.CONSTANT + name )
  }
}

object ClassObject{
  //伴生对象中的私有字段
  private val CONSTANT = "汪汪汪"
  def main(args: Array[String]) {
    val p = new ClassObject
    //访问伴生类的私有字段name
    p.name = "123"
    p.printName()
  }
}

package lagou.cn.part04

//伴生类和伴生对象，它们的名字是一样的，并且必须存在与同一个文件中
class ClassObject{
  private var name="lagou"
  def printerInfo: Unit ={
    //在伴生类中可以访问伴生对象的私有成员
    println(ClassObject.num)
    println("Hello Object!")
  }
}

object ClassObject {
  private val num =10

  def main(args: Array[String]): Unit = {
    val classObject = new ClassObject
    //在伴生对象当中可以访问伴生类的私有成员
    println(classObject.name)
    classObject.printerInfo
  }

}

  3).应用程序对象
  每个Scala应用程序都必须从一个对象的main方法开始，这个方法的类型为 Array[String] => Unit；
  备注：main方法写在class中是没有意义的，在IDEA中这样的 class 连run的图标都不能显示
  除了main方法以外，也可以扩展App特质(trait)

object Hello extends App {
  if (args.length > 0)
    println(s"Hello World; args.length = ${args.length}")
  else
    println("Hello World")
}
  4).apply方法
  object 中有一个非常重要的特殊方法 -- apply方法；
    apply方法通常定义在伴生对象中，目的是通过伴生类的构造函数功能，来实现伴生对象的构造函
数功能；
    通常我们会在类的伴生对象中定义apply方法，当遇到类名(参数1,...参数n)时apply方法会被调用；
    在创建伴生对象或伴生类的对象时，通常不会使用new class/class() 的方式，而是直接使用
class()隐式的调用伴生对象的 apply 方法，这样会让对象创建的更加简洁；

//class Student为伴生类
class Student(name: String, age: Int) {
  private var gender: String = _

  def sayHi(): Unit ={
    println(s"大家好，我是$name,$gender 生")
  }
}

//object Student是class class的伴生对象
object Student {
  //apply方法定义在伴生对象中
  def apply(name: String, age: Int): Student = new Student(name, age)

  def main(args: Array[String]): Unit = {
    //直接利用类名进行对象的创建，这种方式实际上是调用伴生对象的apply方法实现的
    val student=Student("jacky",30)
    student.gender="男"

	student.sayHi()
  }
}

   问题：在Scala中实现工厂方法，让子类声明哪种对象应该被创建，保持对象创建在同一位置。例如，
假设要创建Animal工厂，让其返回Cat和Dog类的实例，基于这个需求，通过实现Animal伴生对象的
apply方法，工厂的使用者可以像这样创建新的Cat和Dog实例。

abstract class Animal {
  def speak
}

class Dog extends Animal {
  override def speak: Unit = {
    println("woof")
  }
}

class Cat extends Animal {
  override def speak: Unit = {
    println("meow")
  }
}

object Animal {
  def apply(str: String): Animal = {
  if (str == "dog")
    new Dog
  else
    new Cat
  }
  
  def main(args: Array[String]): Unit = {
    val cat = Animal("cat")
    cat.speak

	val dog = Animal("dog")
	dog.speak
  }
}
